package localSearch;


import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.TreeMap;

import operations.AssignConstant;
import operations.CreateObject;
import operations.InvokeMethod;
import operations.Operation;
import operations.ValuesGenerator;

import localSearch.LocalSearchPopulation;
import util.GAConfig;
import model.Filter;
import util.GAOperationsConfig;
import util.Log;
import util.DebugConfig;
import util.Randomness;
import model.CUT;
import model.MethodDeclared;
import model.StatementKind;
import model.TestClusterBuilder;
import instrumentation.Condition;
import instrumentation.CoverageBranchTarget;
import instrumentation.TrackerCoverage;
import ga.Individual;
import ga.GAInfo;
import executor.ReflectionExecutor;
public class HillClimbingPure extends LocalSearchPopulation{

	private  TrackerCoverage tracker;
	private  long startTime;
	private  static long elapsed;
	private static long TestTimeout;

	@Override	
	public Individual execute()throws  IOException, ClassNotFoundException{
		System.out.println("	HillClimbing No relation	");
		startTime=gaInfo.getStartTime();
		TestTimeout=gaInfo.getTestTimeOut();
		elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
		
		tracker = TrackerCoverage.getTracker();
		
		int trgtBranch=getTargetToCover(tracker);
		if(trgtBranch==-1) return null;
		Individual chrom =tracker.branchNotCoveredByIndividual.get(trgtBranch);
	   System.out.println("Branch# :" + trgtBranch );//+ " Original chrom: "+chrom.toString() );

			Individual result = hillClimb(chrom,trgtBranch);
			if(result!=null)
				return result;

		return null;
	}
	
	public Map<Class<?>,  List<StatementKind>>  getRecommendMethods(int trgtBranch){ 
		return null;
	}

	public int getTargetToCover(TrackerCoverage tracker)throws  IOException, ClassNotFoundException{
		int trgtBR=-1;
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		List<Integer> keys = new ArrayList<Integer>(trackers.keySet());
			CoverageBranchTarget condTrgt=null;
		int times=0;
		while(trgtBR==-1){
			times++;
			for ( int indx = 0; indx < trackers.size(); indx++) {
				int considerBr=	keys.get(indx);
				if(tracker.branchNotCoveredByIndividual.containsKey(considerBr)){ 
					condTrgt = (CoverageBranchTarget) trackers.get(considerBr);
					Condition c =this.getCondition(considerBr);
					if(c.getIsCallMethod()&&times!=2) continue;
					if(condTrgt==null) continue;

					if( condTrgt.getAttempt()!=gaInfo.getAttempt()){
						trgtBR=considerBr;

						break;
					}

			}
			}//for indx
			if(tracker.branchNotCoveredByIndividual.size()==0)
				return -1;
			if(trgtBR==-1)
				gaInfo.setAttempt(gaInfo.getAttempt()+1);
			else{
				condTrgt = (CoverageBranchTarget) trackers.get(trgtBR);
				condTrgt.setAttempt(gaInfo.getAttempt());
			}
		}
			return trgtBR;
	
	}
	private Condition getCondition(int trgtBR){
		Map<Integer, Condition> mapBranchCondition=gaInfo.getMapBranchCondition();
	       TreeMap<Integer,Condition> sorted_map = new TreeMap<Integer,Condition>(mapBranchCondition);
	       if(sorted_map.containsKey(trgtBR)){
	   			Condition c= sorted_map.get(trgtBR);
	   			return c;
	       }
		return null;
	}
	private static final int ITERATIONS = 500;
	/** Number of attempts when a method is selected */
	private final int TTL_FIRST = 10;
	/** Number of attempts for operations able to reduce the branch distance */
	private final int TTL_IMPROVEMENT  = 20;
	public Individual hillClimb (Individual originalIndividual,int trgtBR){
		
		int ttl = 0;//how many times that a position/ method can be targeted again
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget covCondOrig = (CoverageBranchTarget) trackers.get(trgtBR);
		float origBranchDistFitness=covCondOrig.getQuality();	
		if(covCondOrig==null) return null;

		if(DebugConfig.HCPure)
				Log.log("covCondOrig " + covCondOrig.getQuality() );
		Individual individualMuted=null;
		for (int i = 0; i < ITERATIONS && elapsed < TestTimeout; i++) {
		if(--ttl < 0) {
			individualMuted=originalIndividual.clone();	
			ttl = TTL_FIRST;
		}


		// the mutate operations must change the size of the individual, except in the modify operations that returns the same size of the indiviuals with the original
			boolean canContinue = true;
//			System.out.println("originalIndividual " + originalIndividual.getSize() );

		    canContinue=mutate(individualMuted,trgtBR);

//			System.out.println("individualMuted " +individualMuted.getSize());



			tracker.reset();
			ReflectionExecutor.execute( gaInfo,individualMuted);
			float[] fitness= tracker.calculateHCFitness(individualMuted,trgtBR);
		
			if(!stillFeasible(trgtBR)) continue;

			CoverageBranchTarget mutetedCondOrig = (CoverageBranchTarget) trackers.get(trgtBR);

			if(DebugConfig.HCPure)
						Log.log("mutetedCondOrig " + mutetedCondOrig.getQuality() );
		
//			System.out.println("origBranchDistFitness " + origBranchDistFitness );
//			System.out.println("mutetedCondOrig " + mutetedCondOrig.getQuality() );
			
			if(mutetedCondOrig.getQuality() == 0) {
				System.out.println("Target " + mutetedCondOrig.toString() + " hit");
				Log.info("Target " + mutetedCondOrig.toString() + " hit");
						return individualMuted;
			}
			if(mutetedCondOrig.getQuality() == origBranchDistFitness&& individualMuted.getSize() < originalIndividual.getSize()) {
				originalIndividual=individualMuted.clone();
				originalIndividual.setBranchDistance(originalIndividual.getBranchDistance());
				originalIndividual.setFitness(mutetedCondOrig.getQuality());
			}
//			originalIndividual=individualMuted;
			if(mutetedCondOrig.getQuality()!=origBranchDistFitness){
					origBranchDistFitness=mutetedCondOrig.getQuality();
					ttl = TTL_IMPROVEMENT;
					originalIndividual=individualMuted.clone();
					originalIndividual.setBranchDistance(individualMuted.getBranchDistance());
					originalIndividual.setFitness(individualMuted.getFitness());
				}

			 elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
//			   if(trgtBR==39)


		}
		return null;
	}
	private boolean stillFeasible(int BR){
		if(tracker.individualBestAcievedDistance.containsKey(BR))
			return true;
		
		return false;
	
	}

	/** probability to add an operation(s)  */
	private float probAdd = 0.5f;

	/** probability to remove  an operation(s) */
	private float probRemove = 0.2f;

	/**   probability to modify the parameters of  an operation(s)*/
	private float probModify = 0.3f;


	public boolean mutate(Individual individual,int trgtBR){
		List<Operation> ops = individual.getStatements();
		
		final boolean isModifiable = ops.size() >= 0 ;// && this.isPredicateTypePrimitive(trgtBR);

		final boolean isRemovable = ops.size()>3;

		int choice;
		/** 0 => add; 1 => modify; 2 => remove */
		if(isModifiable && isRemovable) {
			float c = Randomness.random.nextFloat() * (probAdd + probModify + probRemove);
			if(c < probAdd) choice = 0;
			else if(c < probAdd + probModify) choice = 1;
			else choice = 2;
		} else if(isModifiable) {
			float c = Randomness.random.nextFloat()* (probAdd + probModify);
			if(c < probAdd) choice = 0;
			else choice = 1;
		} else if(isRemovable) {
			float c = Randomness.random.nextFloat()* (probAdd + probRemove);
			if(c < probAdd) choice = 0;
			else choice = 2;
		} else choice = 0;
		int pos = Randomness.random.nextInt(ops.size());
		
	//	choice=0;
		if(choice==0){
			if(DebugConfig.HCPure)
				Log.log("Add Method-1: " + individual.toString());
			addOperationsRandomly(ops,trgtBR);
			if(DebugConfig.HCPure)
				Log.log("Add Method-2: " + individual.toString());

			return true;
		}else if(choice==1){
			if(DebugConfig.HCPure)
				Log.log("method modify -1: " + ops.get(pos).toString());

			if (ops.get(pos) instanceof InvokeMethod) {
				Operation mutateOp = mutantMethodParams((InvokeMethod) ops.get(pos),
						individual);
				ops.set(pos, mutateOp);
			} else if (ops.get(pos) instanceof CreateObject) {
				Operation mutateOp = mutantConstructoParams((CreateObject) ops.get(pos), individual);
				ops.set(pos, mutateOp);

			} else if (ops.get(pos) instanceof AssignConstant) {
				Operation mutateOp = mutantAssignConstant(
						(AssignConstant) ops.get(pos), individual);
				ops.set(pos, mutateOp);
			}
			if(DebugConfig.HCPure)
				Log.log("method modify -2: " + ops.get(pos).toString());

			return false;
		}else if(choice==2){
			if(DebugConfig.HCPure)
				Log.log("remove operation-1: " + individual.getSize());
			ops.remove(pos);
			if(DebugConfig.HCPure)
				Log.log("remove operation-2: " + individual.getSize());

			return true;
		}else{
			System.err.println("HillCimbing.mutate: undefined operation");
		}
		return true;
	}

	private void addOperationsRandomly(List<Operation> ops, int trgtBR) {

			int numberMethodsAdded=ValuesGenerator.getRandomIntBetween(1,10 + gaInfo.getAttempt());
			List<Operation> newOps=	Operation.randomlyGenerate(gaInfo.getTestCluster(),numberMethodsAdded);
			ops.addAll(newOps.subList(0, newOps.size()));

		}

	private Operation mutantMethodParams(InvokeMethod op,Individual individual)  {
		Operation.replacementObjectParam= new LinkedList<Class<?>>();
		TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();

		StatementKind m = op.getMethod();
		List<Class<?>> paramsTpe = m.getInputTypes();
		List<Object> oldValues=op.getValues();
		List<Object> newValues=new LinkedList<Object>();

		//Change return reference
			String _return = (((MethodDeclared) m).isVoid()) ? null : Operation.getReference(((MethodDeclared) m).getMethod().getReturnType().getName(), TestCluster.getRefFactory());
			op.setReturn(_return);
		//change parameters
		boolean commitedChange=false;
		for(int i = 0; i < paramsTpe.size(); i++) {
			Class<?> t=paramsTpe.get(i);
				if (Filter.isPrimitiveOrStringType(t))
					newValues.add(ValuesGenerator.modifyPrimitiveValue(t,oldValues.get(i)));
				else
					newValues.add( Operation.generateReference(t, TestCluster));
				commitedChange=true;
		}
		if(commitedChange)
			op.setValues(newValues,Operation.replacementObjectParam);
		
		return op;
	}
	private Operation mutantConstructoParams(CreateObject op,Individual individual)  {
		TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
		StatementKind cons = op.getConstructor();
		List<Class<?>> paramsTpe = cons.getInputTypes();
		List<Object> oldValues=op.getValues();
		List<Object> newValues=new LinkedList<Object>();

		boolean commitedChange=false;
		for(int i = 0; i < paramsTpe.size(); i++) {
			Class<?> t=paramsTpe.get(i);
				if (Filter.isPrimitiveOrStringType(t))
					newValues.add(ValuesGenerator.modifyPrimitiveValue(t,oldValues.get(i)));
				else
					newValues.add( Operation.generateReference(t, TestCluster));
				commitedChange=true;
		}
		if(commitedChange)
			op.setValues(newValues);
		return op;
	}
	private Operation mutantAssignConstant(AssignConstant op,Individual individual)  {
		TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
		Map<String, CUT>  cluster= TestCluster.getCluster();
		String cName= ((AssignConstant) op).getDeclaringClass().getName();
		CUT cut =cluster.get(cName) ;
		String ref_=op.getRefrence();
		op = AssignConstant.generate(cut,TestCluster);
		return op;
	}

	private static void simpleTestExecute(GAInfo     gaInfo,TestClusterBuilder TestCluster)throws ClassNotFoundException{
		  TrackerCoverage tracker=  TrackerCoverage.getTracker();
		
		  List<Operation> initialConstructors= Operation.generateInitialConstructors(TestCluster);

	      List<Operation> ops=new LinkedList<Operation>();
    	  ops.addAll(Operation.randomlyGenerate(TestCluster,GAConfig.TestCaseSize));
	      Individual individual=new Individual(initialConstructors,ops);

			System.out.println(individual.toString());
			
			ReflectionExecutor.execute(gaInfo,individual);
			tracker.calculateFitness(individual,gaInfo.getMapBranchCondition().keySet());

			System.out.println("------------------------");
			  System.out.println(tracker.toString());
				System.out.println(tracker.displayCondition());

	}

	public static void main(String[] args) throws ClassNotFoundException,IOException{
		String logFilePath = "c:/MAMDA/MAMDA2014/temp/HCPure.txt";
		Log.logConfig(logFilePath);
		new File(logFilePath).getAbsolutePath();
	
		for (int i = 0; i <= 0; i += 6) {
			GAInfo gaInfo = new GAInfo();
			Randomness.reset(10);
			long startTime = Calendar.getInstance().getTimeInMillis();
			gaInfo.setTestTimeOut(60 * 500);
	//		 gaInfo.setTestTimeOut( 60 * 1000*1 ); ;

			gaInfo.setStartTime(startTime);

			Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
			// get class from file
			allClasses.addAll(TestClusterBuilder.readClassFromFile());

			TestClusterBuilder TestCluster = new TestClusterBuilder(allClasses);
			gaInfo.setTestCluster(TestCluster);
			GAConfig.UseReadWrietRelation=false;
			LocalSearch HillClimbing = GAOperationsConfig.getLocalSearch();
			HillClimbing.setExtraInfo(gaInfo);
			gaInfo.setMapBranchCondition(HillClimbing.readFileWGZ(gaInfo
					.getTestCluster()));

			System.out.println("Hill Climbing.");

			for (int j = 0; j < 10; j++) {
				simpleTestExecute(gaInfo, TestCluster);
			}
			TrackerCoverage tracker = TrackerCoverage.getTracker();
			System.out.println("------------------------");
			System.out.println(tracker.toString() );
			// System.out.println(tracker.displayCondition());

			int no = tracker.branchNotCoveredByIndividual.size();
			// for (int k = 0; k <=no -1;k++){
			for (int k = 0;  elapsed < gaInfo.getTestTimeOut(); k++) {
				Individual improvedIndividual = HillClimbing.execute();
			}
			System.out.println("------------------------");
			System.out.println(tracker.toString());
			// System.out.println(tracker.displayCondition());
			if(GAConfig.SaveData)
				Log.log(tracker.toString());
		//	 tracker.summarizeCoverage();
		}
			System.exit(0);

	}

}